package com.example;

import com.example.factory.CustomFactoryBean;
import com.example.factory.NormalFactory;
import com.example.pojo.CommonClass;
import com.example.pojo.OtherClass;
import com.example.pojo.PrototypeClass;
import demo.common.util.PrintUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.support.FactoryBeanRegistrySupport;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Arrays;
import java.util.Map;

/**
 * @author yexi
 */
public class Main {

    /**
     * @see org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd)
     * @see FactoryBeanRegistrySupport#doGetObjectFromFactoryBean(FactoryBean factory, String beanName)
     */
    public static void main(String[] args) {
        // classpath:表示该文件从字节码输出路径中寻找。
        String configLocation = "classpath:application.xml";
        //该 ConfigurableApplicationContext 中就存放着容器对象。
        //指定配置文件的位置，提供给spring解析，并创建对应的bean对象，存到容器中，并返回容器对象。
        try (ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(configLocation)) {
            PrintUtils.println("演示获取Bean的各种方式");
            //根据配置的bean的id(id找不到就找name)从容器中获取被创建的对象
            Object common1 = ctx.getBean("common1");
            System.out.println("根据名称查找: " + common1);
            System.out.println("根据名称查找(+强转): " + (CommonClass) common1);

            //当自己没有配置id以及name的时候也是有默认名字的,#0意思其实就是对应返回类型的第一个,同理#1就是第二个
            System.out.println("根据默认名称查找(+强转): " + (CommonClass) ctx.getBean("com.example.pojo.CommonClass#0"));
            System.out.println("根据默认名称查找(+强转): " + (CommonClass) ctx.getBean("com.example.pojo.CommonClass#1"));

            //也可以根据类型来找
            CommonClass common2 = null;
            try {
                common2 = ctx.getBean(CommonClass.class);
            } catch (NoUniqueBeanDefinitionException e) {
                //这个异常的意思就是根据类型来找时虽然找到了,但同时找到了多个bean,它不知道你具体要哪个所以报错了
                PrintUtils.printException(e);
                //出现这种情况的话可以同时按照类型和名字来寻找，亦或者配置某一个bean为primary(后面演示)
                common2 = ctx.getBean("common", CommonClass.class);
            }
            //可以看到输出的地址与通过common1获取的bean一致，也就是说容器创建好bean之后我们获取的就是同一个对象，这就是Spring容器的最大特点：实现单例。
            System.out.println("根据类型查找: " + common2);

            PrintUtils.println();
            //找到所有在容器中配置好的bean的id(id优先级高点，如果没有配置id则找name)
            System.out.println("容器中已配置的所有bean的id列表: " + Arrays.toString(ctx.getBeanDefinitionNames()));

            //根据配置的bean的类型从容器中获取对应的所有map集合(从这个api可以清晰地看出在容器中它们就是以键值对形式存在的)
            Map<String, CommonClass> beansOfCommonClass = ctx.getBeansOfType(CommonClass.class);
            System.out.println("指定CommonClass类型在容器中已配置的所有bean: " + beansOfCommonClass);
            Map<String, OtherClass> beansOfOtherClass = ctx.getBeansOfType(OtherClass.class);
            System.out.println("指定OtherClass类型在容器中已配置的所有bean: " + beansOfOtherClass);
            System.out.println("自定义Bean工厂本身的bean: " + ctx.getBeansOfType(CustomFactoryBean.class));
            System.out.println("普通工厂类被创建的bean: " + ctx.getBeansOfType(NormalFactory.class));
            System.out.println("通过自定义Bean工厂创建的bean对象: " + (OtherClass) ctx.getBean("factoryBean"));
            System.out.println("主动通过自定义Bean工厂创建的bean对象: " + (OtherClass) ctx.getBean("other"));
            System.out.println("主动调用普通工厂类的工厂方法创建的bean对象: " + (OtherClass) ctx.getBean("other2"));
            System.out.println("容器中存在多个该类型的bean，获取被指定为primary的bean: " + (OtherClass) ctx.getBean(OtherClass.class));

            //测试多例
            PrintUtils.println("测试多例");
            //地址不一样，并不是相同的bean。且不存入容器，只在使用时辅助初始化并创建。
            System.out.println("配置多例bean在容器中的体现: " + ctx.getBeansOfType(PrototypeClass.class));
            System.out.println("配置多例bean在容器中的体现: " + (PrototypeClass) ctx.getBean("prototype"));

            PrintUtils.println("多例测试完毕");
        }
    }
}
